#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "entities.h"
#include "definitions.h"
#include "helpers.h"

char *msg;

/*
Purpose	: get the argument type
How : check the given str by the assembler rules
Parameters : agrument to check, command index, registers list, commands list
return value : the instruction type (enum ArgumentType)
*/
ArgumentType GetArgumentType(char *str, int *cmdIndex,RegistersList *regs, CommandsList *cmds)
{
	/*check if not empty*/
	if(strlen(str) > 0)
	{
		/*check if comment*/
		if(str[0] == ';')
			return Comment;
		/*check if instruction and return the instruction type*/
		else if(str[0] == '.')
		{
			if(strcmp(str, ".string") ==0 )
				return InstructionString;
			if(strcmp(str, ".data") == 0)
				return InstructionData;
			if(strcmp(str, ".entry") == 0)
				return InstructionEntry;
			if(strcmp(str, ".extern") == 0)
				return InstructionExtern;
		}
		/*check if label*/
		else if(IsLabel(str,regs, cmds))
			return Label;
		/*check if command*/
		else if(IsCommand(str, cmdIndex, cmds))
			return Command;
		else
			return Unknown;/*otherwise unknown instruction*/

	}
	else /*empty line*/
	{
		printf("Line number %d is empty\n", lineNumber);
		return Empty;
	}
	return Unknown;
}

/*
Purpose	: check if argument is a label
How : check the given str by the assembler rules for label
Parameters : agrument to check, registers list, commands list
return value : true if label otherwise false (enum boolean)
*/
boolean IsLabel(char *str, RegistersList *regs, CommandsList *cmds)
{
	/*if text length is not exceeded then maximum of 30*/
	if(strlen(str) > LABEL_L)
		return false;
	if(str[strlen(str)-1] == ':')/*label must ends with :*/
	{
		if(!IsValidLabel(str, cmds, regs))/*check if label is valid*/
		{
			printf("error\n");
			return false;
		}
		return true;
	}

	return false;
}

/*
Purpose	: check if label is valid
How : check the given str by the assembler rules for label
Parameters : argument to check, commands list, registers list
return value : true if label is valid otherwise false (enum boolean)
*/
boolean IsValidLabel(char *str, CommandsList *cmds, RegistersList *regs)
{
	size_t i;
	/*label name cannot be equal to command name or register name*/
	if(IsCommandExists(str, cmds) || IsRegister(0, str, regs))
		return false;
	/*label must starts with big letter*/
	if((str[0] >= 'A' && str[0]<='Z') || (str[0]>='a' && str[0] <='z'))
	{
		for(i=1; i < strlen(str)-1; i++)
		{
			/*label can be combined with big/small letters or numbers*/
			if((str[i] >= 'A' && str[i]<='Z') || (str[i]>='a' && str[i] <='z') || (str[i] >='0' && str[i] <='9'))
				continue;
			else
			{
				printf("Label is not in the correct format. Label must start with capital letter and combined with big/small letters or numbers\n");
				return false;
			}
		}
		return true;
	}

	return false;
}

/*
Purpose	: check if argument is a command
How : search the given argument in commands list
Parameters : agrument to check, commands list
return value : true if argument is command otherwise false (enum boolean)
*/
boolean IsCommand(char *str, int *cmdIndex, CommandsList* cmds)
{
	char *ch;
	int i;
	/*get command name*/
	ch = strchr(str,'/');
	if(!ch)
		return 0;

	for(i=0; i< COMMANDS_NUMBER; i++)
	{
		/*search the command name in commands list*/
		if(strncmp(cmds[i].name, str, strlen(cmds[i].name)) == 0)
		{
			/*update command index*/
			*cmdIndex = i;
			return true;
		}
	}
	return false;

}

/*
Purpose	: check if argument is a register
How : search the given argument in registers list
Parameters : agrument to check, registers list
return value : true if argument is register otherwise false (enum boolean)
*/
boolean IsRegister(int *regIndex, char *name, RegistersList *regs)
{
	int i;
	for(i=0; i < REGISTERS_NUMBER; i++)
	{
		/*search rregister name in registers list*/
		if(strncmp(regs[i].name, name,REGISTER_L) == 0)
		{
			/*update register index*/
			*regIndex = i;
			return true;
		}
	}
	return false;
}

/*check if number is valid, return true if number is valid otherwise return false*/
boolean IsValidNumber(char *str, int *number)
{
	int res;
	/*text length must be at least 2, for example #3*/
	if(strlen(str) < 2)
		return false;
	/*text ,ust start with #*/
	if(str[0]!='#')
		return false;
	/*get number after # sign*/
	res = sscanf(str +1, "%d", number);
	if(!res)
		return false;

	return true;
}

/*
Purpose	: check if operand address method is indexed
How : check the given argument by the operands address methods rules
Parameters : argument (operand) to check, operand, data index, registrers list, commands list
			 operand and data index are being updated
return value : true if address method is indexed otherwise false (enum boolean)
*/
boolean IsIndexed(char *str, char *label, char *operand, char *dataIndex, RegistersList *regs, CommandsList *cmds)
{
	char *start;
	char *end;
	int res=0;

	/*indexed must start with { sign*/
	start = strrchr(str, '{');
	if(start)
	{
		/*and ends with } sign*/
		end = strrchr(str, '}');
		if(end)
		{
			/*get label before brackets*/
			memset(label, 0, COMMAND_LINE_L);
			strncpy(label, str, start-str);
			if(!label)
				return false;

			/*check if label is valid*/
			if(!IsValidLabel(label, cmds, regs))
				return false;

			/*get the expression in brackets*/
			memset(operand, 0, COMMAND_LINE_L);
			strncpy(operand, start+1, end - start - 1);
			if(operand)
			{
				/*operand must start with ! sign*/
				if(operand[0] == '!' && strlen(operand) >1)
				{
					/*get index*/
					memset(dataIndex, 0, COMMAND_LINE_L);
					res = sscanf(operand + 1, "%s", dataIndex);
					if(!res)
						return false;
					if(*dataIndex < 0 )/*index must be positive*/
						return false;
					if (!IsValidLabel(label, cmds, regs)) // data index must be valid label
						return false;
				}
				else
				{
					return false;
				}
			}

			return true;
		}
	}
	return false;
}

/*
Purpose: find label in label's list by name
return index of label or -1 if not found
*/
int FindLabel(char *labelName, LabelsList *labels)
{
	int i;
	for (i = 0; i < LABELS_NUMBER && strlen(labels[i].name) > 0; ++i)
	{
		if (strncmp(labelName, labels[i].name, strlen(labels[i].name)) == 0)
		{
			return i;
		}
	}
	return -1;
}

/*
Purpose: find extern label in extern labels list by name
return index of label or -1 if not found
*/
int FindExternLabel(char *labelName, ExternLabelsList *labels)
{
	int i;
	for (i = 0; i < LABELS_NUMBER && strlen(labels[i].name); i++)
	{
		if (strncmp(labelName, labels[i].name, strlen(labels[i].name)) == 0)
		{
			return i;
		}
	}
	return -1;
}

/*
Purpose	: get the label address for the given label
How : search the given label in labels list and in external labels list
Parameters : label to search, labels list, external labels list
return value : label address type of integer
*/
int GetLabelAddress(char *str, LabelsList *labels, ExternLabelsList *externs)
{
	int index;

	index = FindLabel(str, labels);
	if (index >= 0)
	{
		return labels[index].decimalAddress;
	}

	if (FindExternLabel(str, externs) >= 0);
	{
		return -2;
	}

	return -1;
}

/*
Purpose	: check if command exists
How : search the given command in commands list
Parameters : command name, commands list
return value : true if command exists in commands list otherwise false (enum boolean)
*/
boolean IsCommandExists(char *name, CommandsList *cmds)
{
	int i;
	for(i=0; i<COMMANDS_NUMBER; i++)
	{
		if(strncmp(cmds[i].name, name, strlen(name))==0)
			return true;
	}
	return false;
}

/*
Purpose	: check if operand label is valid
How : check if operand label is valid by the operand rules
Parameters : operand label, registers list, commands list
return value : true if operand label is otherwise false (enum boolean)
*/
boolean IsValidOperandLabel(char *str, RegistersList *regs, CommandsList *cmds)
{
	size_t i;
	/*operand label cannot be equals to command or register names*/
	if(IsCommandExists(str, cmds) || IsRegister(0, str, regs))
		return false;
	/*operand label must starts with big letter*/
	if((str[0] >= 'A' && str[0]<='Z') || (str[0]>='a' && str[0] <='z'))
	{

		for(i=1; i < strlen(str) && str[i]!='\n' && str[i]!='\r'; i++)
		{
			if((str[i] >= 'A' && str[i]<='Z') || (str[i]>='a' && str[i] <='z') || (str[i] >='0' && str[i] <='9'))
				continue;
			else
			{
				return false;
			}
		}
		return true;
	}

	return false;
}

/*
Purpose	: check if operand has a legal address method according to page 33
How : according to page 33
Parameters : command index, operand type, address method
return value : true if address method is legal otherwise false (enum boolean)
*/
boolean IsLegalAddressingMethod(int cmdIndex, OperandType opType, AddressingMethod method)
{
	switch(cmdIndex)
	{
		case 0:/*mov*/
		case 2:/*add*/
		case 3:/*sub*/
		{
			if(opType == Source) /*all address methods are allowed*/
				return true;
			else /*if destination operand*/
			{
				if(method != Immediate) /*only immediate is not allowed*/
					return true;
			}
			return false;
		}
		case 1:/*cmp*/
		{
			return true; /*all address methods are allowed*/
		}
		case 4:	/*not*/
		case 5:	/*clr*/
		case 7:	/*inc*/
		case 8:	/*dec*/
		case 9:	/*jmp*/
		case 10:/*bne*/
		case 11:/*red*/
		{
			if(opType == Destination)/*no source operand*/
			{
				if(method != Immediate)/*only immediate method is not allowed*/
					return true;
			}
			return false;
		}
		case 6:/*lea*/
		{
			if(method != Immediate)/*only immidiate method is not allowed for both operads source and destination*/
				return true;
			return false;
		}
		case 12:/*prn*/
		{
			if(opType == Destination)/*no source operand*/
			{
				return true; /*all address methods are allowed*/
			}
			return false;
		}
		case 13:/*jsr*/
		{
			if(opType == Destination)/*no source operand*/
				if(method == Direct)/*only direct address is allowed*/
					return true;
			return false;
		}
		case 14:/*rts*/
		case 15:/*stop*/
		{
			return true;
		}
	}
	return false;
}

/*
Purpose: convert number in base 10 to number in base 8
How:
Parameters: number type of long
return value: number in base 8 type of long
 */
long DecToBase8(long num)
{
	int outputBase = 8;
	long base8Num = 0;
	int power;

	power =1;
	while(num > 0)
	{
		base8Num += power*(num%outputBase);
		num/=outputBase;
		power*=10;
	}
	return base8Num;
}

/*
Purpose: write the object file with ob extension
Parameters: file path, objects list, memory data, instruction counter, data counter
return value: none
 */
void WriteObjectFile(char *filePath, ObjectsList *objs, long *memory, int instructC, int dataC)
{
	int i;
	FILE *file;
	char line[LINE_L];
	long address;
	long machineCode;
	long icBase8=0;
	long dcBase8=0;

	file = fopen(filePath, "w");
	if(file)
	{
		icBase8 = DecToBase8(instructC);
		dcBase8 = DecToBase8(dataC);
		sprintf(line, "\t%ld %ld\n", icBase8, dcBase8);
		fputs(line, file);

		for(i=0; i < instructC; i++)
		{
			address = DecToBase8(objs[i].decimalAddress);
			machineCode = DecToBase8(objs[i].machineCode);
			sprintf(line, "%ld\t%.7ld\t\t%c\n", address, machineCode , objs[i].type[0]);
			fputs(line, file);
		}

		for(i=0; i < dataC; i++)
		{
			address = DecToBase8(BASE_ADDRESS + i + instructC);
			machineCode = DecToBase8(memory[i]);
			sprintf(line, "%ld\t%.7ld\n", address, machineCode);
			fputs(line, file);
		}
		fclose(file);
	}
}

/*
Compare two instances of EntriesLabelsList by decimalAddress
*/
int CompareEntriesLabels(const void * a, const void * b)
{
	if (((EntriesLabelsList*)a)->decimalAddress < ((EntriesLabelsList*)b)->decimalAddress) return -1;
	if (((EntriesLabelsList*)a)->decimalAddress == ((EntriesLabelsList*)b)->decimalAddress) return 0;
	if (((EntriesLabelsList*)a)->decimalAddress > ((EntriesLabelsList*)b)->decimalAddress) return 1;
}

/*
Purpose: write the entry file with ent extension
Parameters: file path, entries labels list, entries counter
return value: none
 */
void WriteEntryFile(char *filePath, EntriesLabelsList *ents, int entC)
{
	// print entries sorted by address and NOT by the appearance in the .as file
	qsort(ents, entC, sizeof(EntriesLabelsList), CompareEntriesLabels);

	int i;
	long base8;
	char line[LINE_L];
	FILE *file;

	file = fopen(filePath, "w");
	if(file)
	{
		for(i=0; i < entC; i++)
		{
			base8 = DecToBase8(ents[i].decimalAddress);
			sprintf(line, "%s\t%ld\n", ents[i].name, base8);
			fputs(line, file);
		}
		fclose(file);
	}
}

/*
Purpose: write the extern file with ext extension
Parameters: file path, external labels list, externals counter
return value: none
 */
void WriteExternFile(char *filePath, ExternLabelsList *exts, int extC)
{
	int i;
	long base8;
	char line[LINE_L];
	FILE *file;

	file = fopen(filePath, "w");
	if(file)
	{
		for(i=0; i < extC; i++)
		{
			base8 = DecToBase8(exts[i].decimalAddress);
			sprintf(line, "%s\t%ld\n", exts[i].name, base8);
			fputs(line, file);
		}
		fclose(file);
	}
}

/*
Purpose: print the correct execute instruction
Parameters: none
return value: none
 */
void Usage()
{
	printf("Assembler");
	printf("\n\n");
	printf("Usage : ./assembler <file1> [<file2> <file3> ...]\n\n");
}

